/**
 * Parse the time to string
 *
 * @param {Object | string | number} time
 * @param {string} cFormat
 * @returns {string | null}
 */
import { getServiceBaseURL } from '@/utils/service';
import { useRouteStore } from '@/store/modules/route/index';

export function parseTime(time, cFormat) {
  if (arguments.length === 0 || !time) {
    return null;
  }
  const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}';
  let date;
  if (typeof time === 'object') {
    date = time;
  } else {
    if (typeof time === 'string') {
      if (/^[0-9]+$/.test(time)) {
        // support "1548221490638"
        time = Number.parseInt(time);
      } else {
        // support safari
        // https://stackoverflow.com/questions/4310953/invalid-date-in-safari
        time = time.replace(new RegExp(/-/gm), '/');
      }
    }

    if (typeof time === 'number' && time.toString().length === 10) {
      time *= 1000;
    }
    date = new Date(time);
  }
  const formatObj = {
    y: date.getFullYear(),
    m: date.getMonth() + 1,
    d: date.getDate(),
    h: date.getHours(),
    i: date.getMinutes(),
    s: date.getSeconds(),
    a: date.getDay()
  };
  const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
    const value = formatObj[key];
    // Note: getDay() returns 0 on Sunday
    if (key === 'a') {
      return ['日', '一', '二', '三', '四', '五', '六'][value];
    }
    return value.toString().padStart(2, '0');
  });
  return time_str;
}

/**
 * @param {number} time
 * @param {string} option
 * @returns {string}
 */
export function formatTime(time, option) {
  if (`${time}`.length === 10) {
    time = Number.parseInt(time) * 1000;
  } else {
    time = +time;
  }
  const d = new Date(time);
  const now = Date.now();

  const diff = (now - d) / 1000;

  if (diff < 30) {
    return '刚刚';
  } else if (diff < 3600) {
    // less 1 hour
    return `${Math.ceil(diff / 60)}分钟前`;
  } else if (diff < 3600 * 24) {
    return `${Math.ceil(diff / 3600)}小时前`;
  } else if (diff < 3600 * 24 * 2) {
    return '1天前';
  }
  if (option) {
    return parseTime(time, option);
  }
  return `${d.getMonth() + 1}月${d.getDate()}日${d.getHours()}时${d.getMinutes()}分`;
}

/**
 * @param {string} url
 * @returns {Object}
 */
export function getQueryObject(url) {
  url = url == null ? window.location.href : url;
  const search = url.substring(url.lastIndexOf('?') + 1);
  const obj = {};
  const reg = /([^?&=]+)=([^?&=]*)/g;
  search.replace(reg, (rs, $1, $2) => {
    const name = decodeURIComponent($1);
    let val = decodeURIComponent($2);
    val = String(val);
    obj[name] = val;
    return rs;
  });
  return obj;
}

/**
 * @param {string} input value
 * @returns {number} output value
 */
export function byteLength(str) {
  // returns the byte length of an utf8 string
  let s = str.length;
  for (let i = str.length - 1; i >= 0; i--) {
    const code = str.charCodeAt(i);
    if (code > 0x7f && code <= 0x7ff) s++;
    else if (code > 0x7ff && code <= 0xffff) s += 2;
    if (code >= 0xdc00 && code <= 0xdfff) i--;
  }
  return s;
}

/**
 * @param {Array} actual
 * @returns {Array}
 */
export function cleanArray(actual) {
  const newArray = [];
  for (let i = 0; i < actual.length; i++) {
    if (actual[i]) {
      newArray.push(actual[i]);
    }
  }
  return newArray;
}

/**
 * @param {Object} json
 * @returns {Array}
 */
export function param(json) {
  if (!json) return '';
  return cleanArray(
    Object.keys(json).map(key => {
      if (json[key] === undefined) return '';
      return `${encodeURIComponent(key)}=${encodeURIComponent(json[key])}`;
    })
  ).join('&');
}

/**
 * @param {string} url
 * @returns {Object}
 */
export function param2Obj(url) {
  const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ');
  if (!search) {
    return {};
  }
  const obj = {};
  const searchArr = search.split('&');
  searchArr.forEach(v => {
    const index = v.indexOf('=');
    if (index !== -1) {
      const name = v.substring(0, index);
      const val = v.substring(index + 1, v.length);
      obj[name] = val;
    }
  });
  return obj;
}

/**
 * @param {string} val
 * @returns {string}
 */
export function html2Text(val) {
  const div = document.createElement('div');
  div.innerHTML = val;
  return div.textContent || div.innerText;
}

/**
 * Merges two objects, giving the last one precedence
 *
 * @param {Object} target
 * @param {Object | Array} source
 * @returns {Object}
 */
export function objectMerge(target, source) {
  if (typeof target !== 'object') {
    target = {};
  }
  if (Array.isArray(source)) {
    return source.slice();
  }
  Object.keys(source).forEach(property => {
    const sourceProperty = source[property];
    if (typeof sourceProperty === 'object') {
      target[property] = objectMerge(target[property], sourceProperty);
    } else {
      target[property] = sourceProperty;
    }
  });
  return target;
}

/**
 * @param {HTMLElement} element
 * @param {string} className
 */
export function toggleClass(element, className) {
  if (!element || !className) {
    return;
  }
  let classString = element.className;
  const nameIndex = classString.indexOf(className);
  if (nameIndex === -1) {
    classString += `${className}`;
  } else {
    classString = classString.substr(0, nameIndex) + classString.substr(nameIndex + className.length);
  }
  element.className = classString;
}

/**
 * @param {string} type
 * @returns {Date}
 */
export function getTime(type) {
  if (type === 'start') {
    return new Date().getTime() - 3600 * 1000 * 24 * 90;
  }
  return new Date(new Date().toDateString());
}

/**
 * @param {Function} func
 * @param {number} wait
 * @param {boolean} immediate
 * @returns {any}
 */
export function debounce(func, wait, immediate) {
  let args;
  let context;
  let result;
  let timeout;
  let timestamp;

  const later = function () {
    // 据上一次触发时间间隔
    const last = +new Date() - timestamp;

    // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
    if (last < wait && last > 0) {
      timeout = setTimeout(later, wait - last);
    } else {
      timeout = null;
      // 如果设定为immediate===true，因为开始边界已经调用过了此处无需调用
      if (!immediate) {
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      }
    }
  };

  return function (...args) {
    context = this;
    timestamp = +new Date();
    const callNow = immediate && !timeout;
    // 如果延时不存在，重新设定延时
    if (!timeout) timeout = setTimeout(later, wait);
    if (callNow) {
      result = func.apply(context, args);
      context = args = null;
    }

    return result;
  };
}

/**
 * This is just a simple version of deep copy Has a lot of edge cases bug If you want to use a perfect deep copy, use
 * lodash's _.cloneDeep
 *
 * @param {Object} source
 * @returns {Object}
 */
export function deepClone(source) {
  if (!source && typeof source !== 'object') {
    throw new Error('error arguments', 'deepClone');
  }
  const targetObj = source.constructor === Array ? [] : {};
  Object.keys(source).forEach(keys => {
    if (source[keys] && typeof source[keys] === 'object') {
      targetObj[keys] = deepClone(source[keys]);
    } else {
      targetObj[keys] = source[keys];
    }
  });
  return targetObj;
}

/**
 * @param {Array} arr
 * @returns {Array}
 */
export function uniqueArr(arr) {
  return Array.from(new Set(arr));
}

/** @returns {string} */
export function createUniqueString() {
  const timestamp = `${+new Date()}`;
  const randomNum = `${Number.parseInt((1 + Math.random()) * 65536)}`;
  return (+(randomNum + timestamp)).toString(32);
}

/**
 * Check if an element has a class
 *
 * @param {HTMLElement} elm
 * @param {string} cls
 * @returns {boolean}
 */
export function hasClass(ele, cls) {
  return Boolean(ele.className.match(new RegExp(`(\\s|^)${cls}(\\s|$)`)));
}

/**
 * Add class to element
 *
 * @param {HTMLElement} elm
 * @param {string} cls
 */
export function addClass(ele, cls) {
  if (!hasClass(ele, cls)) ele.className += ` ${cls}`;
}

/**
 * Remove class from element
 *
 * @param {HTMLElement} elm
 * @param {string} cls
 */
export function removeClass(ele, cls) {
  if (hasClass(ele, cls)) {
    const reg = new RegExp(`(\\s|^)${cls}(\\s|$)`);
    ele.className = ele.className.replace(reg, ' ');
  }
}

// 由一个组件，向上找到最近的指定组件
export function findComponentUpward(context, componentName) {
  let parent = context.$parent;
  let name = parent.$options.name;

  while (parent && (!name || ![componentName].includes(name))) {
    parent = parent.$parent;
    if (parent) name = parent.$options.name;
  }
  return parent;
}

// 获取指定名称的子组件,获取到组件后通过proxy调用组件方法
export function findComponentDownward(context, componentName) {
  let components = [];
  components = findComponentsDownward(context, componentName);
  return components.length ? components[0] : null;
}

// 由一个组件，向下找到所有指定组件
export function findComponentsDownward(instance, componentName) {
  const results = [];

  // 检查VNode是否是目标组件
  function isTargetComponent(vnode) {
    if (!vnode || !vnode.type) return false;

    // 处理组件选项对象
    const type = typeof vnode.type === 'object' ? vnode.type : vnode.type.__vccOpts;
    return type && type.name === componentName && vnode.component;
  }

  // 递归遍历VNode树
  function traverseVNode(vnode) {
    if (!vnode) return;

    // 检查当前节点是否为目标组件
    if (isTargetComponent(vnode)) {
      results.push(vnode.component);
    }

    // 处理不同类型的子节点
    if (Array.isArray(vnode.children)) {
      // 数组类型的子节点（template常用）
      vnode.children.forEach(child => traverseVNode(child));
    }
    // 处理VNode对象的子节点（render函数常用）
    else if (vnode.children && typeof vnode.children === 'object' && vnode.children.type) {
      traverseVNode(vnode.children);
    }

    // 处理组件实例的子树
    if (vnode.component && vnode.component.subTree) {
      traverseVNode(vnode.component.subTree);
    }

    // 处理插槽内容
    if (vnode.component && vnode.component.slots) {
      Object.values(vnode.component.slots).forEach(slot => {
        if (Array.isArray(slot)) {
          slot.forEach(item => traverseVNode(item));
        } else if (slot && typeof slot === 'object') {
          traverseVNode(slot);
        }
      });
    }
  }

  // 从当前实例的根节点开始遍历
  if (instance && instance.subTree) {
    traverseVNode(instance.subTree);
  }

  return results;
}

// 找到指定组件的兄弟组件
export function findBrothersComponent(context, componentName) {
  // Vue3 方式 - 通过父组件的 refs 查找兄弟组件
  const parent = context.$parent;
  if (!parent || !parent.$refs) return null;

  return Object.values(parent.$refs).find(ref => {
    return ref && typeof ref === 'object' && ref.$options && ref.$options.name === componentName;
  });
}

export function listToTree(list, typeid = 'TYPEID', id = 'ID', pid = null) {
  const isValidate =
    Array.isArray(list) &&
    list.every(function (item) {
      return Object.hasOwn(item, typeid) && Object.hasOwn(item, id);
    });
  if (!isValidate) {
    // console.error(`缺少key:${typeid},${id}`)
    return list;
  }
  const treeNodes = list
    .filter(item => {
      if (Array.isArray(pid)) {
        return pid.includes(`${item[typeid]}`.trim());
      }
      return `${item[typeid]}`.trim() == pid;
    })
    .sort((pre, next) => {
      return pre.order_id >= next.order_id ? 1 : -1;
    })
    .map(item => {
      const children = listToTree(list, typeid, id, item[id]);
      if (children && children.length) {
        item.children = children;
      }
      return item;
    });
  return treeNodes;
}

// 获取模板实例
export function getTemplateInstance(context) {
  return (
    findComponentUpward(context, 'Tem1101') ||
    findComponentUpward(context, 'Tem1102') ||
    findComponentUpward(context, 'Tem1110') ||
    findComponentUpward(context, 'Tem1111') ||
    findComponentUpward(context, 'Tem1120') ||
    findComponentUpward(context, 'Tem1130') ||
    findComponentUpward(context, 'Tem1140') ||
    findComponentUpward(context, 'Tem1150') ||
    findComponentUpward(context, 'Tem1160') ||
    findComponentUpward(context, 'Tem1170')
  );
}

// 获取子表实例
export function getSubTableInstance(context) {
  return findComponentUpward(context, 'ComplexGrid');
}

// 获取上一个tableOperate
export function getPTableoperateInstance(context) {
  return findComponentUpward(context, 'TableOperate');
}

/**
 * Promise.all不进入catch
 *
 * @param {p} items promise数组
 * @returns
 */
export function catchFun(items) {
  return items.map(item => item.catch(err => Promise.resolve(err)));
}

/**
 * // 获取当前文件夹下所有文件
 *
 * @param {any} dir 目录
 * @param {any} regExp 匹配文件正则
 * @param {any} useSub 表示是否检索子文件夹
 * @returns array
 */
export function getFiles(directory, regExp = /.vue$/, useSub = true) {
  const files = require.context(directory, useSub, regExp);
  const modules = [];
  files.keys().forEach(item => {
    modules.push(files(item));
  });
  return modules;
}

export function getType(arg) {
  return Object.prototype.toString.call(arg);
}

export function isObject(arg) {
  return getType(arg) === '[object Object]';
}

export function isBoolean(arg) {
  return getType(arg) === '[object Boolean]';
}

export function isNull(arg) {
  return getType(arg) === '[object Null]';
}

export function isArray(arg) {
  return Array.isArray(arg);
}

export function isString(arg) {
  return getType(arg) === '[object String]';
}

export function isFunction(arg) {
  return getType(arg) === '[object Function]';
}

export function flatObject(data) {
  const uid = `uid_${Date.now()}`;
  const process = (input, prefix = '', json = {}) => {
    Object.keys(input).forEach(key => {
      const val = input[key];
      const newKey = key;

      if (!json.hasOwnProperty(`${prefix}${newKey}.`)) {
        json[`${prefix}${newKey}.`] = uid;
      }

      if (typeof val === 'object' && !Array.isArray(val) && Boolean(val)) {
        json[`${prefix}${newKey}.`] = val;
        process(val, `${prefix}${newKey}.`, json);
      } else {
        json[`${prefix}${newKey}.`] = val;
      }
    });

    return json;
  };

  const json = process(data);
  const result = {};
  Object.keys(json).forEach(key => {
    if (json[key] !== uid) {
      result[key.slice(0, -1)] = json[key];
    }
  });
  return result;
}

/** 展开多维为一维 USERID: { code: '', name: '' } => USERID.code: '', USERID.name: '' */
export function flatTableData(tableData) {
  return tableData.map(item => {
    return flatObject(item);
  });
}

export function getDic(typeid) {
  return window.$CodeSource[typeid].sort((a, b) => {
    return a.orderId >= b.orderId ? 1 : -1;
  });
}

// 有父子关系的数组，转为树形结构
export function arrayToTree(arr, pid = '0', primaryKey = 'code', parentKey = 'pcode') {
  const result = [];
  const treeArr = arr;
  treeArr.forEach((item, i) => {
    if (item[parentKey] == pid) {
      const a = arrayToTree(treeArr, item[primaryKey], primaryKey, parentKey);
      if (a.length > 0) {
        // 递归调用此函数
        treeArr[i].children = a;
      }
      result.push(treeArr[i]);
    }
  });
  return result;
}

/** 判断是否显示refdata 只有6、7、8、10类型才会显示 */
export function tableShowRefdata(eleType) {
  return ['6', '7', '8', '9', '10'].includes(eleType);
}

export function randomString(length) {
  const str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  let result = '';
  for (let i = length; i > 0; --i) {
    result += str[Math.floor(Math.random() * str.length)];
  }
  return result;
}

export function randomSlotName(length = 8) {
  const str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  let result = '';
  for (let i = length; i > 0; --i) {
    result += str[Math.floor(Math.random() * str.length)];
  }
  return result;
}

export function collectPluck(oriObj, key) {
  const tmp = [];
  oriObj.forEach(value => {
    tmp.push(value[key]);
  });
  return tmp;
}

export function getRoute(code) {
  const routeStore = useRouteStore();
  let route = null;
  const routes = routeStore.menus;
  routes.forEach(i => {
    if (i.id === code) {
      route = i;
    } else if (i.children && i.children.length) {
      i.children.forEach(ii => {
        if (ii.id === code) {
          route = ii;
        } else if (ii.children && ii.children.length) {
          ii.children.forEach(iii => {
            if (iii.id === code) {
              route = iii;
            } else if (iii.children && iii.children.length) {
              iii.children.forEach(iiii => {
                if (iiii.id === code) {
                  route = iiii;
                }
              });
            }
          });
        }
      });
    }
  });
  return route;
}

export function isDev() {
  return process.env.NODE_ENV === 'development';
}

export function getLocalUrl(url) {
  const domain = `${location.protocol}//${location.hostname}`;
  const match = url.match(/:(\d+)/);
  if (match && match.length) {
    return `${domain}:${match[1]}`;
  }
  return domain;
}

/** Base64 编码工具，完美的支持 unicode 编码的字符串 Base64 加密解密使用方法： Base64.encode('中文') --> 5Lit5paH Base64.decode('5Lit5paH') --> 中文 */
export var Base64 = {
  // private property
  _keyStr: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
  // public method for encoding
  encode(input) {
    let output = '';
    let chr1;
    let chr2;
    let chr3;
    let enc1;
    let enc2;
    let enc3;
    let enc4;
    let i = 0;
    input = Base64._utf8_encode(input);
    while (i < input.length) {
      chr1 = input.charCodeAt(i++);
      chr2 = input.charCodeAt(i++);
      chr3 = input.charCodeAt(i++);
      enc1 = chr1 >> 2;
      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
      enc4 = chr3 & 63;
      if (isNaN(chr2)) {
        enc3 = enc4 = 64;
      } else if (isNaN(chr3)) {
        enc4 = 64;
      }
      output =
        output +
        this._keyStr.charAt(enc1) +
        this._keyStr.charAt(enc2) +
        this._keyStr.charAt(enc3) +
        this._keyStr.charAt(enc4);
    }
    return output;
  },
  // public method for decoding
  decode(input) {
    let output = '';
    let chr1;
    let chr2;
    let chr3;
    let enc1;
    let enc2;
    let enc3;
    let enc4;
    let i = 0;
    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
    while (i < input.length) {
      enc1 = this._keyStr.indexOf(input.charAt(i++));
      enc2 = this._keyStr.indexOf(input.charAt(i++));
      enc3 = this._keyStr.indexOf(input.charAt(i++));
      enc4 = this._keyStr.indexOf(input.charAt(i++));
      chr1 = (enc1 << 2) | (enc2 >> 4);
      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
      chr3 = ((enc3 & 3) << 6) | enc4;
      output += String.fromCharCode(chr1);
      if (enc3 != 64) {
        output += String.fromCharCode(chr2);
      }
      if (enc4 != 64) {
        output += String.fromCharCode(chr3);
      }
    }
    output = Base64._utf8_decode(output);
    return output;
  },
  // private method for UTF-8 encoding
  _utf8_encode(string) {
    string = string.replace(/\r\n/g, '\n');
    let utftext = '';
    for (let n = 0; n < string.length; n++) {
      const c = string.charCodeAt(n);
      if (c < 128) {
        utftext += String.fromCharCode(c);
      } else if (c > 127 && c < 2048) {
        utftext += String.fromCharCode((c >> 6) | 192);
        utftext += String.fromCharCode((c & 63) | 128);
      } else {
        utftext += String.fromCharCode((c >> 12) | 224);
        utftext += String.fromCharCode(((c >> 6) & 63) | 128);
        utftext += String.fromCharCode((c & 63) | 128);
      }
    }
    return utftext;
  },
  // private method for UTF-8 decoding
  _utf8_decode(utftext) {
    let string = '';
    let i = 0;
    let c = (c1 = c2 = 0);
    while (i < utftext.length) {
      c = utftext.charCodeAt(i);
      if (c < 128) {
        string += String.fromCharCode(c);
        i++;
      } else if (c > 191 && c < 224) {
        c2 = utftext.charCodeAt(i + 1);
        string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
        i += 2;
      } else {
        c2 = utftext.charCodeAt(i + 1);
        c3 = utftext.charCodeAt(i + 2);
        string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
        i += 3;
      }
    }
    return string;
  }
};

export function merge(target, ...arg) {
  return arg.reduce((acc, cur) => {
    return Object.keys(cur).reduce((subAcc, key) => {
      const srcVal = cur[key];
      if (isObject(srcVal)) {
        subAcc[key] = merge(subAcc[key] ? subAcc[key] : {}, srcVal);
      } else if (isArray(srcVal)) {
        // series: []，下层数组直接赋值
        subAcc[key] = srcVal.map((item, idx) => {
          if (isObject(item)) {
            const curAccVal = subAcc[key] ? subAcc[key] : [];
            return merge(curAccVal[idx] ? curAccVal[idx] : {}, item);
          }
          return item;
        });
      } else {
        subAcc[key] = srcVal;
      }
      return subAcc;
    }, acc);
  }, target);
}

export function getUrlParam(name) {
  const url = window.location.href;
  // 通过 ? 分割获取后面的参数字符串
  const urlStr = url.split('?')[1] || '';
  // 创建空对象存储参数
  const obj = {};
  // 再通过 & 将每一个参数单独分割出来
  const paramsArr = urlStr.split('&');
  for (let i = 0, len = paramsArr.length; i < len; i++) {
    // 再通过 = 将每一个参数分割为 key:value 的形式
    const arr = paramsArr[i].split('=');
    obj[arr[0]] = arr[1];
  }
  return obj[name] || null;
}

export function windowClose() {
  if (navigator.userAgent.includes('Firefox') || navigator.userAgent.includes('Chrome')) {
    window.location.href = 'about:blank';
    window.close();
  } else {
    window.opener = null;
    window.open('', '_self');
    window.close();
  }
}

// 获取多层级数据
export function getDeepData(data, attr, defaultValue = '') {
  try {
    const matchValue = attr.split('.').reduce((data, key) => data[key], data);
    defaultValue = typeof matchValue !== 'undefined' ? matchValue : defaultValue;
    return defaultValue;
  } catch (e) {
    return '';
  }
}

export const loadView = view => {
  // 路由懒加载
  return resolve => require([`@/views/${view}`], resolve);
};

export function getAppId() {
  // getUrlParam为了兼容应用预览
  return getUrlParam('appId') || sessionStorage.getItem('appId') || window?.g.appId || '';
}
export function getBaseApi() {
  const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
  const { otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
  return otherBaseURL.api;
}
